package content

import (
	"context"
	"gf_blog_admin/internal/dao"
	"gf_blog_admin/internal/model"
	"gf_blog_admin/internal/service"
	"gf_blog_admin/utility/utils"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/os/gtime"
)

type sArticle struct{}

func init() {
	service.RegisterArticle(NewArticle())
}

func NewArticle() *sArticle {
	return &sArticle{}
}

func (s *sArticle) List(ctx context.Context, in *model.ArticleListInput) (out []*model.ArticleListItem, total int, err error) {
	var (
		cls = dao.BlArticle.Columns()
		m   = dao.BlArticle.Ctx(ctx)
	)

	if in.Keywords != "" {
		m = m.WhereLike(cls.ArticleTitle, in.Keywords+"%")
	}
	if in.Status != "" {
		m = m.Where(cls.ArticleStatus, utils.StrToInt(in.Status))
	}
	if in.CategoryId != "" {
		m = m.Where(cls.CategoryId, utils.StrToInt(in.CategoryId))
	}
	if in.Recycle {
		m = m.Unscoped().Where(cls.IsDeleted, 1)
	}

	out = make([]*model.ArticleListItem, 0)
	err = m.OrderDesc(cls.ArticleSort).OrderDesc(cls.Id).
		Fields(
			cls.Id, cls.CoverImage, cls.ArticleTitle, cls.ArticleSort, cls.ArticleStatus,
			cls.PublishAt, cls.IsTop, cls.ViewCount, cls.LikeCount, cls.DeletedAt,
		).
		Page(in.Page, in.PageSize).
		ScanAndCount(&out, &total, false)
	return
}

func (s *sArticle) Detail(ctx context.Context, id uint64) (out *model.ArticleItem, err error) {
	err = dao.BlArticle.Ctx(ctx).Where(dao.BlArticle.Columns().Id, id).Scan(&out)
	if err != nil {
		return nil, err
	}
	if out == nil {
		return nil, gerror.New("文章不存在")
	}

	out.TagsIds = s.getTagIds(ctx, id)
	out.ArticleContent = s.getContent(ctx, id)
	return
}

func (s *sArticle) Store(ctx context.Context, in *model.ArticleFormInput) (err error) {
	if in.PublishAt == nil {
		in.PublishAt = gtime.Now()
	}
	err = dao.BlArticle.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		var lastId int64
		lastId, err = dao.BlArticle.Ctx(ctx).Data(in).InsertAndGetId()
		if err != nil {
			return err
		}

		aid := uint64(lastId)
		// 添加文章内容
		if err = s.syncContent(ctx, aid, in.ArticleContent); err != nil {
			return err
		}
		// 添加标签
		in.TagsIds = utils.UniqueIds(in.TagsIds)
		if err = s.attachTags(ctx, aid, in.TagsIds); err != nil {
			return err
		}
		return nil
	})
	return
}

func (s *sArticle) Update(ctx context.Context, id uint64, in *model.ArticleFormInput) (err error) {
	if _, err = s.Detail(ctx, id); err != nil {
		return err
	}

	err = dao.BlArticle.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		cls := dao.BlArticle.Columns()
		if _, err = dao.BlArticle.Ctx(ctx).Where(cls.Id, id).Data(in).Update(); err != nil {
			return err
		}
		// 更新文章内容
		if err = s.syncContent(ctx, id, in.ArticleContent); err != nil {
			return err
		}
		// 更新关联标签
		in.TagsIds = utils.UniqueIds(in.TagsIds)
		if err = s.syncTags(ctx, id, in.TagsIds); err != nil {
			return err
		}
		return nil
	})
	return
}

func (s *sArticle) Destroy(ctx context.Context, ids []uint64) (err error) {
	var (
		check bool
		cls   = dao.BlArticle.Columns()
		m     = dao.BlArticle.Ctx(ctx).Unscoped().Where(cls.IsDeleted, 0)
	)

	ids = utils.UniqueIds(ids)
	if check, err = m.WhereIn(cls.Id, ids).Exist(); err != nil {
		return err
	} else if !check {
		return gerror.New("操作失败")
	}

	return dao.BlArticle.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		_, err = m.WhereIn(cls.Id, ids).Data(map[string]interface{}{
			cls.IsDeleted: 1, cls.DeletedAt: gtime.Now(),
		}).Update()
		return err
	})
}

func (s *sArticle) Recover(ctx context.Context, ids []uint64) (err error) {
	var (
		check bool
		cls   = dao.BlArticle.Columns()
		m     = dao.BlArticle.Ctx(ctx).Unscoped().Where(cls.IsDeleted, 1)
	)

	ids = utils.UniqueIds(ids)
	if check, err = m.WhereIn(cls.Id, ids).Exist(); err != nil {
		return err
	} else if !check {
		return gerror.New("操作失败")
	}

	return dao.BlArticle.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		_, err = m.WhereIn(cls.Id, ids).Data(map[string]interface{}{
			cls.IsDeleted: 0, cls.DeletedAt: nil,
		}).Update()
		return err
	})
}

func (s *sArticle) Remove(ctx context.Context, ids []uint64) (err error) {
	var (
		check bool
		cls   = dao.BlArticle.Columns()
		m     = dao.BlArticle.Ctx(ctx).Unscoped().Where(cls.IsDeleted, 1)
	)

	ids = utils.UniqueIds(ids)
	if check, err = m.WhereIn(cls.Id, ids).Exist(); err != nil {
		return err
	} else if !check {
		return gerror.New("操作失败")
	}

	return dao.BlArticle.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		if _, err = m.WhereIn(cls.Id, ids).Delete(); err != nil {
			return err
		}
		// 删除文章内容
		if err = s.delContent(ctx, ids); err != nil {
			return err
		}
		// 删除标签
		if err = s.detachTags(ctx, ids); err != nil {
			return err
		}
		return nil
	})
}

func (s *sArticle) getTagIds(ctx context.Context, aid uint64) (data []uint64) {
	var (
		m        = dao.BlArticleTag.Ctx(ctx)
		table    = dao.BlArticleTag.Table()
		cls      = dao.BlArticleTag.Columns()
		hasTable = dao.BlArticleHasTag.Table()
		hasCls   = dao.BlArticleHasTag.Columns()
	)

	ids, err := m.FieldsPrefix(table, cls.Id).
		InnerJoinOnFields(hasTable, cls.Id, "=", hasCls.TagId).
		WherePrefixIn(hasTable, hasCls.ArticleId, aid).
		Array()
	if err != nil {
		return
	}
	if ids != nil {
		for _, id := range ids {
			data = append(data, id.Uint64())
		}
	}
	return
}

func (s *sArticle) attachTags(ctx context.Context, aid uint64, tagIds []uint64) error {
	var (
		cls  = dao.BlArticleHasTag.Columns()
		data []map[string]interface{}
	)

	if tagIds == nil || len(tagIds) == 0 {
		return nil
	}

	for _, id := range tagIds {
		data = append(data, map[string]interface{}{
			cls.ArticleId: aid,
			cls.TagId:     id,
		})
	}

	_, err := dao.BlArticleHasTag.Ctx(ctx).Data(data).Insert()
	return err
}

func (s *sArticle) detachTags(ctx context.Context, arIds []uint64) error {
	_, err := dao.BlArticleHasTag.Ctx(ctx).WhereIn(dao.BlArticleHasTag.Columns().ArticleId, arIds).Delete()
	return err
}

func (s *sArticle) syncTags(ctx context.Context, aid uint64, tagIds []uint64) error {
	err := s.detachTags(ctx, []uint64{aid})
	if err != nil {
		return err
	}
	err = s.attachTags(ctx, aid, tagIds)
	return err
}

func (s *sArticle) getContent(ctx context.Context, aid uint64) string {
	cls := dao.BlArticleContent.Columns()
	content, err := dao.BlArticleContent.Ctx(ctx).
		Where(cls.ArticleId, aid).
		Fields(cls.ArticleContent).
		Value()
	if err != nil {
		return ""
	}
	return content.String()
}

func (s *sArticle) syncContent(ctx context.Context, aid uint64, con string) error {
	cls := dao.BlArticleContent.Columns()
	_, err := dao.BlArticleContent.Ctx(ctx).
		Data(map[string]interface{}{
			cls.ArticleId: aid, cls.ArticleContent: con,
		}).
		Save()
	return err
}

func (s *sArticle) delContent(ctx context.Context, arIds []uint64) error {
	cls := dao.BlArticleContent.Columns()
	_, err := dao.BlArticleContent.Ctx(ctx).WhereIn(cls.ArticleId, arIds).Delete()
	return err
}
